package com.dm.cloud.utils.minio;

import com.dm.cloud.utils.http.HttpUtils;
import com.google.common.collect.HashMultimap;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.*;
import lombok.extern.log4j.Log4j2;
import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

@Log4j2
@Configuration
@ConditionalOnProperty(prefix = "custom.minio", name = "enable", havingValue = "true")
@EnableConfigurationProperties(MinioProperties.class)
public class MinioUtils {

    private static MinioProperties properties;

    public static String DEFALUT_BUCKET = "clouddefault";

    public static String FILE_WAREHOUSE = "filewarehouse";

    private static CustomMinioClient customMinioClient;

    @Autowired
    public void setProperties(MinioProperties properties) {
        this.properties = properties;
    }

    /**
     * 获取文件信息
     * @param urlData
     * @return
     * @throws Exception
     */
    public static Response getMinio(Map<String,String> urlData) throws Exception {
        String minioLoc = properties.getUrl();
        String bucket = DEFALUT_BUCKET;
        String fileName = "";

        if(urlData.containsKey("minioLoc")){
            minioLoc=urlData.get("minioLoc");
        }
        if(urlData.containsKey("bucket")){
            bucket=urlData.get("bucket");
        }
        if(!urlData.containsKey("fileName")){
           throw new RuntimeException("缺少文件名信息");
        }else{
            fileName=urlData.get("fileName");
        }
        minioLoc=minioLoc.endsWith("/")?minioLoc.substring(0,minioLoc.length()-1):minioLoc;
        String url = minioLoc+"/"+bucket+"/"+fileName;

        return HttpUtils.doGet(url);
    }

    public static Map<String,String> upload(InputStream inputStream) throws Exception {
        return upload(inputStream,"");
    }

    public static Map<String,String> upload(InputStream inputStream, String contentType) throws Exception {
        //默认bucket
        String bucket = DEFALUT_BUCKET; //bucket要小写
        //生成随机文件名称
        String fileName = UUID.randomUUID().toString().replaceAll("-", "");
        return upload(inputStream,contentType,bucket,fileName);
    }

    public static Map<String,String> upload(InputStream inputStream, String contentType, String bucket, String fileName) throws Exception {
        ObjectWriteResponse objectWriteResponse = null;

        Map<String,String> result =new HashMap<>();

        //构建minioClient
        MinioClient minioClient = null;

        minioClient = MinioClient.builder()
                .endpoint(properties.getUrl())
                .credentials(properties.getAccessKey(), properties.getSecureKey())
                .build();

        checkBucket(minioClient,false,bucket);

        PutObjectArgs.Builder stream = createStream(inputStream,contentType,bucket,fileName);
        objectWriteResponse = minioClient.putObject(stream.build());

        result.put("minioLoc",properties.getUrl());
        result.put("bucket",bucket);
        result.put("fileName",fileName);
        result.put("fileUrl",objectWriteResponse.region());
        return result;
    }

    /**
     * 单文件签名上传
     *
     * @param objectName 文件全路径名称
     * @return /
     */
    public static String getUploadObjectUrl(String bucketName, String objectName) {
        // 上传文件时携带content-type头即可
        /*if (StrUtil.isBlank(contentType)) {
            contentType = "application/octet-stream";
        }
        HashMultimap<String, String> headers = HashMultimap.create();
        headers.put("Content-Type", contentType);*/
        try {
            customMinioClient = new CustomMinioClient(MinioAsyncClient.builder()
                    .endpoint(properties.getUrl())
                    .credentials(properties.getAccessKey(), properties.getSecureKey())
                    .build());
            return customMinioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.PUT)
                            .bucket(bucketName)
                            .object(objectName)
                            .expiry(1, TimeUnit.DAYS)
                            //.extraHeaders(headers)
                            .build()
            );
        } catch (Exception e) {
            return null;
        }
    }

    /**
     *  初始化分片上传
     *
     * @param objectName 文件全路径名称
     * @param partCount 分片数量
     * @return /
     */
    public static Map<String, Object> initMultiPartUpload(String bucketName,String objectName, int partCount,String contentType) {
        Map<String, Object> result = new HashMap<>();
        try {
            //如果类型使用默认流会导致无法预览
            contentType = "application/octet-stream";

            HashMultimap<String, String> headers = HashMultimap.create();
            headers.put("Content-Type", contentType);
            customMinioClient = new CustomMinioClient(MinioAsyncClient.builder()
                    .endpoint(properties.getUrl())
                    .credentials(properties.getAccessKey(), properties.getSecureKey())
                    .build());
            checkAsyncBucket(customMinioClient,false,bucketName);
            String uploadId = customMinioClient.initMultiPartUpload(bucketName, null, objectName, headers, null);

            result.put("uploadId", uploadId);
            List<String> partList = new ArrayList<>();

            Map<String, String> reqParams = new HashMap<>();
            reqParams.put("uploadId", uploadId);
            for (int i = 1; i <= partCount; i++) {
                reqParams.put("partNumber", String.valueOf(i));
                String uploadUrl = customMinioClient.getPresignedObjectUrl(
                        GetPresignedObjectUrlArgs.builder()
                                .method(Method.PUT)
                                .bucket(bucketName)
                                .object(objectName)
                                .expiry(1, TimeUnit.DAYS)
                                .extraQueryParams(reqParams)
                                .build());
                partList.add(uploadUrl);
            }
            result.put("uploadUrls", partList);
        } catch (Exception e) {
            return null;
        }

        return result;
    }

    /**
     * 文件合并
     * @param bucketName
     * @param objectName
     * @param uploadId
     * @return
     */
    public static boolean mergeMultipartUpload(String bucketName, String objectName, String uploadId) {
        try {
            Part[] parts = new Part[1000];
            /**
             *  最大分片1000
             */
            customMinioClient = new CustomMinioClient(MinioAsyncClient.builder()
                    .endpoint(properties.getUrl())
                    .credentials(properties.getAccessKey(), properties.getSecureKey())
                    .build());
            ListPartsResponse partResult = customMinioClient.listMultipart(bucketName, null, objectName, 1000, 0, uploadId, null, null);
            int partNumber = 1;
            for (Part part : partResult.result().partList()) {
                parts[partNumber - 1] = new Part(partNumber, part.etag());
                partNumber++;
            }
            //合并分片
            customMinioClient.mergeMultipartUpload(bucketName, null, objectName, uploadId, parts, null, null);
        } catch (Exception e) {
            return false;
        }

        return true;
    }


    /**
     * 删除指定分片上传任务
     * @param bucketName
     * @param objectName
     * @param uploadId
     * @return
     */
    public static boolean removeMultipartUpload(String bucketName, String objectName, String uploadId) {
        try {
            /**
             *  最大分片1000
             */
            customMinioClient = new CustomMinioClient(MinioAsyncClient.builder()
                    .endpoint(properties.getUrl())
                    .credentials(properties.getAccessKey(), properties.getSecureKey())
                    .build());
            customMinioClient.removeMultipartUpload(bucketName,null,objectName,uploadId,null,null);
        } catch (Exception e) {
            return false;
        }

        return true;
    }


    /**
     * 批量删除过期分片文件
     * @param bucketName
     * @return
     */
    public static boolean clearMultipartUpload(String bucketName) {
        try {
            /**
             *  最大分片1000
             */
            customMinioClient = new CustomMinioClient(MinioAsyncClient.builder()
                    .endpoint(properties.getUrl())
                    .credentials(properties.getAccessKey(), properties.getSecureKey())
                    .build());
            String contentType = "application/octet-stream";
            HashMultimap<String, String> headers = HashMultimap.create();
            headers.put("Content-Type", contentType);
            customMinioClient.clearMultipartUpload(bucketName,null,headers,null);
        } catch (Exception e) {
            return false;
        }

        return true;
    }

    /**
     * 设置生命周期
     * @param bucket
     * @throws Exception
     */
    public static void addBucketLifeCycleConfiguration(String bucket) {
        try {
            customMinioClient = new CustomMinioClient(MinioAsyncClient.builder()
                    .endpoint(properties.getUrl())
                    .credentials(properties.getAccessKey(), properties.getSecureKey())
                    .build());

            customMinioClient.addBucketLifeCycleConfiguration(bucket);
        } catch (Exception e) {
            log.error("生命周期设置失败");
            throw new RuntimeException("生命周期设置失败");
        }
    }

    /**
     * 删除生命周期
     * @param bucket
     * @throws Exception
     */
    public static void removeBucketLifeCycleConfiguration(String bucket) {
        try {
            customMinioClient = new CustomMinioClient(MinioAsyncClient.builder()
                    .endpoint(properties.getUrl())
                    .credentials(properties.getAccessKey(), properties.getSecureKey())
                    .build());
            GetBucketLifecycleArgs args = new GetBucketLifecycleArgs.Builder().bucket(bucket).build();
            CompletableFuture<LifecycleConfiguration> lc = customMinioClient.getBucketLifecycle(args);
            if(lc.isDone()){
                customMinioClient.deleteBucketLifecycle(new DeleteBucketLifecycleArgs.Builder().bucket(bucket).build());
            }
        } catch (Exception e) {
            log.error("生命周期设置失败");
            throw new RuntimeException("生命周期设置失败");
        }
    }

    /**
     * 获取指定uploadid中已经上传的分片列表
     * @param bucketName
     * @param objectName
     * @param uploadId
     * @return
     */
    public static List<String> getExsitParts(String bucketName, String objectName, String uploadId) {
        List<String> parts = new ArrayList<>();
        try {

            /**
             *  最大分片1000
             */
            customMinioClient = new CustomMinioClient(MinioAsyncClient.builder()
                    .endpoint(properties.getUrl())
                    .credentials(properties.getAccessKey(), properties.getSecureKey())
                    .build());
            ListPartsResponse partResult = customMinioClient.listMultipart(bucketName, null, objectName, 1024, 0, uploadId, null, null);

            for (Part part : partResult.result().partList()) {
                parts.add(part.etag());
            }
            //合并分片
        } catch (Exception e) {
            //
            log.error("查询任务分片错误");
        }

        return parts;
    }

    /**
     * 删除批量文件
     * @param objectName
     * @throws Exception
     */
    public static void removeObjects(String bucketName,List<String> objectName) {
        objectName.stream().forEach(e-> {
            removeObject(bucketName, e);
        });
    }

    /**
     * 删除单个文件
     * @param objectName
     * @throws Exception
     */
    public static void removeObject(String bucketName,String objectName) {
        try {
            //构建minioClient
            MinioClient minioClient = null;

            minioClient = MinioClient.builder()
                    .endpoint(properties.getUrl())
                    .credentials(properties.getAccessKey(), properties.getSecureKey())
                    .build();
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
        }catch (Exception e){
            log.info("文件删除失败" + e.getMessage());
        }
    }

    /**
     * 检查是否存在桶
     * @param minioClient
     * @param versioning
     * @param bucket
     * @throws Exception
     */
    private static void checkBucket(MinioClient minioClient ,boolean versioning, String bucket) throws Exception {

        boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
        if (!exists) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
            //设置Procy属性 默认所有请求都能读取
            String config = "{ " +
                    "    \"Id\": \"Policy1\", " +
                    "    \"Version\": \"2012-10-17\", " +
                    "    \"Statement\": [ " +
                    "        { " +
                    "            \"Sid\": \"Statement1\", " +
                    "            \"Effect\": \"Allow\", " +
                    "            \"Action\": [ " +
                    "                \"s3:ListBucket\", " +
                    "                \"s3:GetObject\" " +
                    "            ], " +
                    "            \"Resource\": [ " +
                    "                \"arn:aws:s3:::"+bucket+"\", " +
                    "                \"arn:aws:s3:::"+bucket+"/*\" " +
                    "            ]," +
                    "            \"Principal\": \"*\"" +
                    "        } " +
                    "    ] " +
                    "}";
            minioClient.setBucketPolicy(
                    SetBucketPolicyArgs.builder().bucket(bucket).config(config).build());
        }
        // 版本控制
        VersioningConfiguration configuration = minioClient.getBucketVersioning(GetBucketVersioningArgs.builder().bucket(bucket).build());
        boolean enabled = configuration.status() == VersioningConfiguration.Status.ENABLED;
        if (versioning && !enabled) {
            minioClient.setBucketVersioning(SetBucketVersioningArgs.builder()
                    .config(new VersioningConfiguration(VersioningConfiguration.Status.ENABLED, null)).bucket(bucket).build());
        } else if (!versioning && enabled) {
            minioClient.setBucketVersioning(SetBucketVersioningArgs.builder()
                    .config(new VersioningConfiguration(VersioningConfiguration.Status.SUSPENDED, null)).bucket(bucket).build());
        }
    }

    /**
     * 检查是否存在桶
     * @param minioClient
     * @param versioning
     * @param bucket
     * @throws Exception
     */
    private static void checkAsyncBucket(MinioAsyncClient minioClient ,boolean versioning, String bucket) throws Exception {

        CompletableFuture<Boolean> exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
        if (exists.isDone() && !exists.get()) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
            //设置Procy属性 默认所有请求都能读取
            String config = "{ " +
                    "    \"Id\": \"Policy1\", " +
                    "    \"Version\": \"2012-10-17\", " +
                    "    \"Statement\": [ " +
                    "        { " +
                    "            \"Sid\": \"Statement1\", " +
                    "            \"Effect\": \"Allow\", " +
                    "            \"Action\": [ " +
                    "                \"s3:ListBucket\", " +
                    "                \"s3:GetObject\" " +
                    "            ], " +
                    "            \"Resource\": [ " +
                    "                \"arn:aws:s3:::"+bucket+"\", " +
                    "                \"arn:aws:s3:::"+bucket+"/*\" " +
                    "            ]," +
                    "            \"Principal\": \"*\"" +
                    "        } " +
                    "    ] " +
                    "}";
            minioClient.setBucketPolicy(
                    SetBucketPolicyArgs.builder().bucket(bucket).config(config).build());
        }
        // 版本控制
        CompletableFuture<VersioningConfiguration> configuration = minioClient.getBucketVersioning(GetBucketVersioningArgs.builder().bucket(bucket).build());
        if(configuration.isDone()) {
            boolean enabled = configuration.get().status() == VersioningConfiguration.Status.ENABLED;
            if (versioning && !enabled) {
                minioClient.setBucketVersioning(SetBucketVersioningArgs.builder()
                        .config(new VersioningConfiguration(VersioningConfiguration.Status.ENABLED, null)).bucket(bucket).build());
            } else if (!versioning && enabled) {
                minioClient.setBucketVersioning(SetBucketVersioningArgs.builder()
                        .config(new VersioningConfiguration(VersioningConfiguration.Status.SUSPENDED, null)).bucket(bucket).build());
            }
        }
    }

    private static PutObjectArgs.Builder createStream(InputStream inputStream,String contentType,String bucket,String fileName) throws IOException {
        if(Strings.isNotBlank(contentType)){
            return PutObjectArgs.builder()
                    .contentType(contentType)
                    //如果知道对象大小，则将-1传递给partSize进行自动检测；
                    .stream(inputStream,inputStream.available(),-1)
                    .bucket(bucket)
                    .object(fileName); //服务器端存储的文件名
        }else{
            return PutObjectArgs.builder()
                    //如果知道对象大小，则将-1传递给partSize进行自动检测；
                    .stream(inputStream,inputStream.available(),-1)
                    .bucket(bucket)
                    .object(fileName); //服务器端存储的文件名
        }
    }
}
